package com.griddroid.computations;

import com.griddroid.exceptions.NonCoherentComputationException;
import com.griddroid.exceptions.NotCalculatedComputationException;

/**
 * A representation of a Sum, fit for the Grid. It is used to calculate the sum
 * over a mathematical formula with a single variable (multiple sums not
 * supported yet) taking values from a given range
 * 
 * @author Charalampos Mavroforakis
 * @author Thomas Ailianos
 * @author Michail Michailidis
 * 
 */
public class Sum extends Computation
{

	private static final long serialVersionUID = 1929051218479825479L;
	private int base;
	private int end;
	private String function;

	/**
	 * 
	 * @param base
	 *            the lower bound of the Sum
	 * @param end
	 *            the upper bound of the Sum
	 * @param function
	 *            the mathematical formula on which to apply the Sum
	 */
	public Sum(int base, int end, String function)
	{
		this.base = base;
		this.end = end;
		this.function = function;
	}

	/**
	 * Just a default constructor with no typical use. It sets the following
	 * values: <code>
	 * base = 0;
	 * end = 0;
	 * function = "";
     * </code>
	 */
	public Sum()
	{
		this.base = 0;
		this.end = 0;
		this.function = "";
	}

	/**
	 * To split a Sum, we just split the range on which it should be evaluated.
	 * So, e.g. if the original sum should be calculated from 0 to 10 if we
	 * split it in 10 parts, each small Sum will be calculated on the range
	 * <i>i</i> to <i>i</i>+1 (with <i>i</i> ranging from 0 to 9)
	 * 
	 * @param parts
	 *            the number of parts to split the Sum into
	 * @return an array of smaller Sums, that result from the original one
	 */
	@Override
	public Sum[] split(int parts)
	{
		int temp_end;
		int temp_begin = this.base;
		int step = (this.end - this.base + 1) / parts;

		if (step == 0)
		{
			// if parts are too many, then split into fewer parts
			parts = this.end - this.base + 1;
			step = 1;
		}

		Sum[] small_sums = new Sum[parts];
		for (int i = 0; i < parts; i++)
		{
			temp_end = (i != parts - 1) ? temp_begin + step - 1 : this.end;
			small_sums[i] = new Sum(temp_begin, temp_end, this.function);
			temp_begin = temp_end + 1;
		}
		return small_sums;
	}

	/**
	 * Merges an array of Sums into a new one. The final result is the sum of
	 * all the calculated sub-sums
	 * 
	 * @param parts
	 *            array with calculated parts from the same computation
	 * @throws NotCalculatedComputationException
	 *             one or more parts of the array has not been calculated
	 * @throws NonCoherentComputationException
	 *             one or more parts of the array is not a Sum
	 */
	@Override
	public void merge(Computation[] parts) throws NotCalculatedComputationException, NonCoherentComputationException
	{
		double sum = 0D;

		for (Computation c : parts)
		{
			if (!(c instanceof Sum))
			{
				throw new NonCoherentComputationException();
			}
			sum += c.getResult();
		}

		super.calculated = true;
		super.result = sum;
	}

	/**
	 * Executes the calculation of the sum. It is applied on the given
	 * mathematical formula with the (single) variable ranging between
	 * (included) the lower and upper bounds
	 * <p>
	 * Does not return anything, just sets the {@link Computation#result result}
	 * value. To get the actual result, make a call on
	 * {@link Computation#getResult() getResult()}
	 */
	@Override
	public void calculate()
	{
		super.calculate();
		char var = '\0';
		for (char c : function.toCharArray())
		{
			if (Character.isLetter(c))
			{
				var = c;
			}
		}
		double res = 0;

		for (int i = base; i <= end; i++)
		{
			res = res + evaluateExpression(new String[] { "" + var }, new double[] { new Double(i) }, function);
		}
		super.result = res;
	}

	/**
	 * 
	 * @return a String (latex like) representation of the sum
	 */
	@Override
	public String toString()
	{
		return "\\sum_{i=" + this.base + "}^{" + this.end + "}{" + this.function + "}";
	}

	/**
	 * 
	 * @return the lower bound of the sum
	 */
	public int getBase()
	{
		return base;
	}

	/**
	 * 
	 * @return the upper bound of the sum
	 */
	public int getEnd()
	{
		return end;
	}

	/**
	 * 
	 * @return the mathematical formula on which the sum should be evaluated
	 */
	public String getFunction()
	{
		return function;
	}

	/**
	 * 
	 * @param base
	 *            the lower bound of the sum
	 */
	public void setBase(int base)
	{
		this.base = base;
	}

	/**
	 * 
	 * @param end
	 *            the upper bound of the sum
	 */
	public void setEnd(int end)
	{
		this.end = end;
	}

	/**
	 * @param function
	 *            string with a mathematical formula. It should have only one
	 *            variable over which to calculate the product (multiple sums
	 *            not yet supported)
	 */
	public void setFunction(String function)
	{
		this.function = function;
	}

	/**
	 * Just a testing function for the Sum alone, to check the functionality of
	 * the class
	 * 
	 * @param args
	 */
	public static void main(String args[])
	{
		Sum s = new Sum(1, 5, "n^3");
		Sum[] sums = s.split(11);
		for (Sum sum : sums)
		{
			sum.calculate();
			System.out.println(sum.toString());
		}

		try
		{
			Sum ss = new Sum();
			ss.merge(sums);
			System.out.println("_____RES: " + ss.getResult());

		}
		catch (NotCalculatedComputationException e)
		{
			e.printStackTrace();
		}
		catch (NonCoherentComputationException e)
		{
			e.printStackTrace();
		}
	}

}
